Jerry's Log

Query Execution Process

contents

SQL 쿼리의 "실행(Run)" 버튼을 누르면 결과가 즉시 나오는 것처럼 보이지만, 그 이면에서는 데이터베이스 엔진이 논리, 수학, 그리고 I/O(입출력) 작업의 복잡한 과정을 수행합니다. 이 과정은 크게 5단계로 나뉩니다.


1. 연결 계층 (The Connection Layer - 핸드셰이크)

쿼리가 엔진에 도달하기 전, 클라이언트(애플리케이션 또는 SQL 툴)는 데이터베이스와 연결해야 합니다.

참고: 이 과정 때문에 커넥션 풀링(Connection Pooling) 이 중요합니다. 쿼리마다 매번 새로운 연결을 만드는 것은 비용이 많이 듭니다. 풀링은 기존 연결을 재사용하여 이 단계를 건너뜁니다.


2. 파서 (The Parser - 번역가)

데이터베이스는 영어와 유사한 SQL 문장을 바로 이해하지 못합니다. 이를 분해해야 하는데, 파서가 두 가지 주요 작업을 수행합니다.

A. 구문 체크 (Syntax Check - 문법)

SQL이 문법적으로 올바르게 작성되었는지 확인합니다.

B. 의미 체크 (Semantics Check - 의미)

문법이 맞아도, 쿼리의 내용이 말이 되는지 확인합니다.

출력물: 이 단계의 결과로 쿼리의 논리적 단계를 나타내는 계층적 구조인 파스 트리(Parse Tree) 가 생성됩니다.


3. 쿼리 옵티마이저 (The Query Optimizer - 두뇌)

RDBMS에서 가장 복잡하고 중요한 부분입니다. SQL은 선언적 언어(Declarative Language) 입니다(어떻게 할지가 아니라, 무엇 을 원하는지만 말함). 어떻게 가져올지는 옵티마이저가 결정합니다.

옵티마이저는 비용 기반 최적화(CBO) 를 사용하여 여러 잠재적인 "실행 계획"을 만들고, 각 계획의 "비용"(CPU 사이클 + 디스크 I/O)을 계산하여 가장 저렴한 것을 선택합니다.

옵티마이저의 주요 결정 사항:

  1. 접근 방법 (Access Methods - 데이터 가져오는 법):
    • Full Table Scan: 모든 행을 다 읽음 (작은 테이블에 적합).
    • Index Seek/Scan: B-Tree 인덱스를 타고 특정 데이터로 바로 점프 (특정 WHERE 조건이 있는 큰 테이블에 적합).
  2. 조인 방법 (Join Methods - 테이블 합치는 법):
    • Nested Loop Join: A 테이블을 루프 돌면서 매번 B 테이블을 검색 (작은 데이터셋).
    • Hash Join: 작은 테이블로 메모리에 해시 맵을 만든 뒤, 큰 테이블과 비교 (대용량 데이터셋).
    • Merge Join: 두 테이블을 먼저 정렬(Sort)한 뒤 지퍼처럼 합침.
  3. 조인 순서 (Join Order):
    • A, B, C 테이블을 조인할 때, A+B를 먼저 할지 B+C를 먼저 할지 결정합니다. 순서에 따라 성능 차이가 큽니다.

결정의 근거: 옵티마이저는 이러한 수학적 추측을 위해 통계 정보(Statistics)(행의 개수, 데이터 분포도, 히스토그램 등)를 사용합니다.

출력물: 실행 계획(Execution Plan) (구체적인 작업 지시서).


4. 쿼리 실행기 (The Query Executor - 실행/근육)

실행기는 실행 계획을 받아 실제로 수행합니다. 일종의 관리자(Coordinator) 역할을 합니다.


5. 스토리지 엔진 (The Storage Engine - 파일 캐비닛)

실제 물리적 데이터가 존재하는 곳입니다. (MySQL의 InnoDB 등). 스토리지 엔진은 데이터의 실제 조회 및 관리를 담당합니다.

A. 버퍼 풀 확인 (캐싱)

디스크를 보기 전에, 엔진은 버퍼 풀(Buffer Pool - RAM) 을 확인합니다.

B. 디스크 I/O

데이터가 디스크에 있다면, 엔진은 "페이지(Page)"(보통 8KB or 16KB 블록) 단위로 데이터를 읽어 버퍼 풀에 적재합니다.

C. 동시성 제어 (잠금)

읽는 동안 엔진은 ACID 속성을 보장합니다.


요약 흐름도

  1. Client: SQL 전송 (SELECT * FROM Users...).
  2. Parser: 철자/테이블 확인 -> 파스 트리 생성.
  3. Optimizer: 수학/통계 계산 -> 실행 계획 생성.
  4. Executor: 계획에 따라 수행 -> 스토리지 엔진에 데이터 요청.
  5. Storage Engine: RAM(버퍼 풀) 확인 -> 디스크 읽기 -> 행(Row) 반환.
  6. Client: 결과 집합(Result Set) 수신.

왜 이것을 알아야 할까요?

이 과정을 이해하면 느린 쿼리를 디버깅할 수 있습니다.

references